# 作者：徐宇杰
# -*- coding = utf-8 -*-
# @Time :2024/7/12 上午9:22
# @File:backtesting02.py
# @Software:PyCharm
import json
from datetime import date

import numpy as np
import pandas as pd
import requests

# 输出更美观好看
# 取消panda科学计数法,保留4位有效小数位.
pd.set_option('float_format', lambda x: '%.2f' % x)

# 数据传输的初步处理
# Token accessToken 及权限校验机制
getAccessTokenUrl = 'https://quantapi.51ifind.com/api/v1/get_access_token'
# 获取refresh_token需下载Windows版本接口包解压，打开超级命令-工具-refresh_token查询
refreshtoken = (
    'eyJzaWduX3RpbWUiOiIyMDI0LTA3LTEwIDEwOjQzOjU3In0=.eyJ1aWQiOiI3MDQyMzg2NjciLCJ1c2VyIjp7ImFjY291bnQiOiJkZHFoMTIwIiwiYXV'
    '0aFVzZXJJbmZvIjp7IkVleGNlbFBheWVycyI6IjE3NDU4OTE5NjkwMDAifSwiY29kZUNTSSI6W10sImNvZGVaekF1dGgiOltdLCJoYXNBSVByZWRpY3Q'
    'iOmZhbHNlLCJoYXNBSVRhbGsiOmZhbHNlLCJoYXNDSUNDIjpmYWxzZSwiaGFzQ1NJIjpmYWxzZSwiaGFzRXZlbnREcml2ZSI6ZmFsc2UsImhhc0ZUU0U'
    'iOmZhbHNlLCJoYXNGYXN0IjpmYWxzZSwiaGFzRnVuZFZhbHVhdGlvbiI6ZmFsc2UsImhhc0hLIjp0cnVlLCJoYXNMTUUiOmZhbHNlLCJoYXNMZXZlbDI'
    'iOmZhbHNlLCJoYXNSZWFsQ01FIjpmYWxzZSwiaGFzVVMiOmZhbHNlLCJoYXNVU0FJbmRleCI6ZmFsc2UsIm1hcmtldENvZGUiOiIxNjszMjsxNDQ7MTc'
    '2OzExMjs4ODs0ODsxMjg7MTY4LTE7MTg0OzIwMDsyMTY7MTA0OzEyMDsxMzY7MjMyOzU2Ozk2OzE2MDs2NDsiLCJtYXhPbkxpbmUiOjEsIm5vRGlzayI'
    '6ZmFsc2UsInByb2R1Y3RUeXBlIjoiU1VQRVJDT01NQU5EUFJPRFVDVCIsInJlZnJlc2hUb2tlbkV4cGlyZWRUaW1lIjoiMjAyNS0wNC0yOSAwOTo1OTo'
    'yOSIsInNlc3NzaW9uIjoiOTVkZDA0MDM3Y2M0OGFiMTE5NWRiODdmMjI2YmM3YzciLCJzaWRJbmZvIjp7fSwidWlkIjoiNzA0MjM4NjY3IiwidXNlclR'
    '5cGUiOiJGUkVFSUFMIiwid2lmaW5kTGltaXRNYXAiOnt9fX0=.7D5E59D38C226AF1E8E3B35B7F905FFF492B62F4FED33A2879D899ABA815B7CA')
getAccessTokenHeader = {"Content- Type": "application/json", "refresh_token": refreshtoken}
getAccessTokenResponse = requests.post(url=getAccessTokenUrl, headers=getAccessTokenHeader)
accessToken = json.loads(getAccessTokenResponse.content)['data']['access_token']
thsHeaders = {"Content-Type": "application/json", "access_token": accessToken}


# 输入的参数
# 1.品种(variety)（默认为上海期货交易所的那只主连期货），例如 "RB"（螺纹钢）、"RU"（橡胶）等（字符串类型）
# 2.开始日期与结束日期(start_date,end_date)(需要字符串类型) 如"2023-08-14 10:00:00"   "2024-07-01 10:00:00"，第一天不能在到期日期内
# 3.数据的频率(frequency) 如1、30等（表示一分钟、三十分钟） 整数类型
# 4.interval(间隔)  整数类型  一种方法的参数        期权选择的方法(option_selection) 默认为最高最低四分之一百位取整数的方法,方法0为默认方法
# 5.止损和止盈的标准(profit_stop,loss_stop) 默认都为0.1 （到期日不计算也为默认,已经在里面实现了）
# 6.分批(quant)功能的实现，如果分批，输入分批的数目（最大值），默认值为1，也就是不分批
def Import_Futures_Data(variety, Start_date, End_date, Frequency):
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    code = variety + "ZL.SHF"
    thsPara = {"codes": code,
               "indicators":
                   "open,high,low,close",
               "starttime": Start_date,
               "endtime": End_date,
               "functionpara": {
                   "Interval": str(Frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data = json.loads(thsResponse.content)
    df = pd.json_normalize(data['tables'])
    df2 = df.set_index(['thscode'])
    un_nested_list = []
    for col in df2.columns:
        un_nested_list.append(df2[col].apply(pd.Series).stack())

    data = pd.concat(un_nested_list, axis=1, keys=df2.columns)
    # result = result.reset_index(drop=True)
    # 设置二级索引
    data = data.reset_index()
    data = data.set_index(['thscode', 'time'])
    # 格式化,行转列
    data = data.drop(columns=['level_1'])
    data = data.reset_index()
    # 设置列索引名称
    data.columns = ['thscode', 'time', 'close', 'open', 'high', 'low']
    # 去除NaN值
    data.dropna(inplace=True)
    # 倒序时间输出
    # data = data.sort_values(by='time', ascending=False)
    data = data.reset_index()
    data = data.iloc[:, 1:]
    return data


def Identifying_Options(data, Variety, Option_Selection_Methods=0):
    range_window = 300  # x个freq
    df = pd.DataFrame(columns=['C_code', 'P_code', 'time', 'close', 'upper', 'lower', 'k_call', 'k_put', 'due'])
    flag = 0
    for i in range(0, len(data) - range_window):
        data_temp = data.iloc[i:i + range_window, ]  # 迭代取观测区间点
        timestamp_open = data_temp.iloc[-1, 1]
        index1 = str(timestamp_open).find('-')
        year = int(str(timestamp_open)[:index1]) - 2000
        index2 = str(timestamp_open).find('-', index1 + 1)
        month = int(str(timestamp_open)[index1 + 1:index2])
        date_str1 = str(timestamp_open)[:10]
        date1 = date.fromisoformat(date_str1)
        if month < 5:
            month = '05'
        elif month < 10:
            month = '10'
        else:
            month = '01'
            year = year + 1
        upper = data_temp['high'].max()
        lower = data_temp['low'].min()
        new_row = {'close': data_temp.iloc[-1, 2], 'upper': upper, 'lower': lower, 'time': timestamp_open}
        df.loc[flag] = new_row
        if Option_Selection_Methods == 0:
            UpDown = upper - lower
            otm_range = int(UpDown / 4)  # 找出对称区间
            K_call = round((df.iloc[flag, 3] + otm_range) / 100) * 100  # 百位取整
            K_put = round((df.iloc[flag, 3] - otm_range) / 100) * 100  # 百位取整
            str_list = list(Variety + ".SHF")
            df.iloc[flag, 6] = K_call
            str_list.insert(-4, "{}C{}".format(str(year) + month, K_call))
            code_call = ''.join(str_list)
            df.iloc[flag, 0] = code_call
        else:
            print("其他方法还没有实现，尽情期待！")
        # 利用http调用股权的到期日日期
        if flag == 0 or df.loc[flag - 1, 'C_code'] != code_call:
            thsUrl = 'https://quantapi.51ifind.com/api/v1/date_sequence'
            thsPara = {"codes": code_call,
                       "startdate": date_str1,
                       "enddate": date_str1,
                       "functionpara":
                           {"Fill": "Blank"},
                       "indipara":
                           [
                               {
                                   "indicator":
                                       "ths_last_strike_date_option"
                               }
                           ]
                       }
            thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
            str_date1 = json.loads(thsResponse.content)
            str_date2 = pd.json_normalize(str_date1['tables'])
            str_date = str_date2.reset_index(drop=True)
            date_str = str_date.iloc[0, 2]
            date_str2 = date_str[0]
            date2 = date.fromisoformat(date_str2)

        str_list = list(Variety + ".SHF")
        df.iloc[flag, 7] = K_put
        str_list.insert(-4, "{}P{}".format(str(year) + month, K_put))
        code_put = ''.join(str_list)
        df.iloc[flag, 1] = code_put
        if date1 < date2:
            df.loc[flag, 'due'] = 0
        else:
            df.loc[flag, 'due'] = 1
        flag = flag + 1
    return df


def Data_Calculate(df, Frequency, Profit_Taking_Standard=0.1, Stop_Loss_Standard=-0.1, quant=1):
    df[['action', 'call_sell_price', 'call_market_price', 'average_call_change', 'put_sell_price', 'put_market_price',
        'average_put_change', 'sum_change']] = np.zeros((len(df), 8))
    df = df.sort_values(by='time', ascending=True)
    df = df.reset_index()
    df = df.iloc[:, 1:]

    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    # action在最后的表中是列9
    action = 1
    length = len(df)
    count = 0
    sum_income = 0
    number_of_profit_exits = 0
    number_of_stop_losses = 0
    for i in range(0, length):
        # 判断条件：期权到期
        if df.loc[i, 'due'] == 1:
            count = count + 1
            df.iloc[i, 0] = np.nan
            df.iloc[i, 1] = np.nan
            df.iloc[i, 4] = np.nan
            df.iloc[i, 5] = np.nan
            df.iloc[i, 6] = np.nan
            df.iloc[i, 7] = np.nan
            continue
        if count != 0:
            action = action + 1
            count = 0
        df.iloc[i, 9] = action
        # 根据action的值来做出相应的判断，第一次进来特殊判断
        if i == 0 or df.iloc[i, 9] != df.iloc[i - 1, 9]:
            arr1 = np.zeros(quant, dtype=float)
            arr2 = np.zeros(quant, dtype=float)
            temp = quant
            # 根据所需数据来填充表格中的数据
            # 看涨数据
            thsPara = {"codes": df.iloc[i, 0],
                       "indicators": "close",
                       "starttime": df.iloc[i, 2],
                       "endtime": df.iloc[-1, 2],
                       "functionpara": {
                           "Interval": str(Frequency),
                           "Fill": "Original"
                       }
                       }
            thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
            data0 = json.loads(thsResponse.content)
            data1 = pd.json_normalize(data0['tables'])
            un_nested_lst = []
            for col in data1.columns:
                un_nested_lst.append(data1[col].apply(pd.Series).stack())
            data2 = pd.concat(un_nested_lst, axis=1, keys=data1.columns)
            data2 = data2.reset_index(drop=True)
            data2 = data2.iloc[:, 1:]
            length1 = len(data2)
            min_length = min(length - i, length1)
            df.iloc[i:min_length + i, 11] = data2.iloc[0:min_length, 1]
            # df.fillna(0)
            # 看跌数据
            thsPara = {"codes": df.iloc[i, 1],
                       "indicators": "close",
                       "starttime": df.iloc[i, 2],
                       "endtime": df.iloc[-1, 2],
                       "functionpara": {
                           "Interval": str(Frequency),
                           "Fill": "Original"
                       }
                       }
            thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
            data0 = json.loads(thsResponse.content)
            data1 = pd.json_normalize(data0['tables'])
            un_nested_lst = []
            for col in data1.columns:
                un_nested_lst.append(data1[col].apply(pd.Series).stack())
            data2 = pd.concat(un_nested_lst, axis=1, keys=data1.columns)
            data2 = data2.reset_index(drop=True)
            data2 = data2.iloc[:, 1:]
            length2 = len(data2)
            min_length = min(length - i, length2)
            df.iloc[i:min_length + i, 14] = data2.iloc[0:min_length, 1]
            # df.fillna(0)
            df.iloc[i, 10] = df.iloc[i, 11]
            df.iloc[i, 13] = df.iloc[i, 14]
            arr1[0] = df.iloc[i, 11]
            arr2[0] = df.iloc[i, 14]
            temp = temp - 1
        elif df.iloc[i, 9] == df.iloc[i - 1, 9]:
            df.iloc[i, 0] = np.nan
            df.iloc[i, 1] = np.nan
            df.iloc[i, 4] = np.nan
            df.iloc[i, 5] = np.nan
            df.iloc[i, 6] = np.nan
            df.iloc[i, 7] = np.nan
            # 实现多批的功能
            if temp == 0:
                df.iloc[i, 10] = np.nan
                df.iloc[i, 13] = np.nan
            else:
                df.iloc[i, 10] = df.iloc[i, 11]
                df.iloc[i, 13] = df.iloc[i, 14]
                arr1[quant - temp] = df.iloc[i, 11]
                arr2[quant - temp] = df.iloc[i, 14]
                temp = temp - 1

        # 计算参数，填表
        sum1 = 0
        sum2 = 0
        number = 0
        for j in range(quant):
            if arr1[j] != 0:
                sum1 = sum1 + arr1[j]
                sum2 = sum2 + arr2[j]
                number = number + 1
        average1 = float(sum1) / number
        average2 = float(sum2) / number
        df.iloc[i, 12] = average1 - df.iloc[i, 11]
        df.iloc[i, 15] = average2 - df.iloc[i, 14]
        df.iloc[i, 16] = df.iloc[i, 12] + df.iloc[i, 15]

        # 判断条件：指的是两个期权卖出去的价差总和除以两个期权卖价总和，如果大于Profit_Taking_Standard或者小于Stop_Loss_Standard
        if df.iloc[i, 16] >= 0:
            if df.iloc[i, 16] / (sum1 + sum2) >= Profit_Taking_Standard:
                number_of_profit_exits = number_of_profit_exits + 1
                action = action + 1
                sum_income = sum_income + df.iloc[i, 16] * number
        else:
            if df.iloc[i, 16] / (sum1 + sum2) <= Stop_Loss_Standard:
                number_of_stop_losses = number_of_stop_losses + 1
                action = action + 1
                sum_income = sum_income + df.iloc[i, 16] * number
    return sum_income, df, number_of_profit_exits, number_of_stop_losses


def backtest(Variety, Start_date, End_date, Frequency=30, Option_Selection_Methods=0, Profit_Taking_Standard=0.1,
             Stop_Loss_Standard=-0.1, Quant=1):
    # 对期货数据进行导入，并且进行初始化
    data = Import_Futures_Data(Variety, Start_date, End_date, Frequency)
    # 通过期货数据与期权的选择方法来确定期权数据的取值，并且依据期权的到期日期来判断是否到期
    df = Identifying_Options(data, Variety)
    # 通过对市场价的调取并且依据标准系数的计算，得到最终收益值
    income, result, number_of_profit_exits, number_of_stop_losses = Data_Calculate(df, Frequency,
                                                                                   Profit_Taking_Standard,
                                                                                   Stop_Loss_Standard, Quant)
    # 对结果进行一个总的输出和保存
    result = result.reset_index()
    result = result.iloc[:, 1:]
    save_path = "D:\大四下\实习\大地期货\测试" + Variety + "回测数据结果表.xlsx"
    strings = ["选择的品种为" + Variety, "开始时间为" + result.iloc[0, 2] + "  结束时间为" + result.iloc[-1, 2],
               "时间的频率为" + str(Frequency) + "分钟", "期权的选择办法为方法" + str(Option_Selection_Methods),
               "止盈的比例为" + str(Profit_Taking_Standard) + "  止损的比例为" + str(Stop_Loss_Standard),
               "止盈的次数为" + str(number_of_profit_exits) + "次  止损的比例为" + str(number_of_stop_losses) + "次",
               "总收入为" + str(income) + "元"]
    df = pd.DataFrame(strings, columns=['Parameter'])
    with pd.ExcelWriter(save_path, engine='openpyxl') as writer:
        result.to_excel(writer, sheet_name='sheet1', index=False)
        df.to_excel(writer, sheet_name='Sheet2', index=False)


def main():
    backtest("RB", "2023-07-12 11:00:00", "2024-07-01 10:00:00", 30, 0,
             0.1, -0.1, 4)


if __name__ == '__main__':
    main()
